﻿using System;
using System.Collections.Generic;
using System.Text;
using Godot.core.io;
using Godot.core;

namespace Godot.modules.gdscript
{
    public partial class GDTokenizerBuffer/*cont.*/
    {
        private const int TOKEN_BYTE_MASK = 0x80;
        private const int TOKEN_BITS = 8;
        private const int TOKEN_MASK = (1 << TOKEN_BITS) - 1;
        private const int TOKEN_LINE_BITS = 24;
        private const int TOKEN_LINE_MASK = (1 << TOKEN_LINE_BITS) - 1;
        private const int BYTECODE_VERSION = 1;

        public Error set_code_buffer(List<byte> p_buffer)
        {
            return set_code_buffer(p_buffer.ToArray());
        }

        public Error set_code_buffer(byte[] p_buffer)
        { 
            unsafe
            {
                fixed (byte* buf = p_buffer)
                {
                    uint total_len = (uint)p_buffer.Length;

                    if (p_buffer.Length < 24 || p_buffer[0] != 'G' || p_buffer[1] != 'D' || p_buffer[2] != 'S' || p_buffer[3] != 'C')
                        return Error.ERR_INVALID_DATA;

                    uint version = Marshalls.decode_uint32(&buf[4]);

                    if (version > BYTECODE_VERSION)
                    {
                        return Error.ERR_INVALID_DATA;
                    }
                    
                    uint identifier_count = Marshalls.decode_uint32(&buf[8]);
                    uint constant_count = Marshalls.decode_uint32(&buf[12]);
                    uint line_count = Marshalls.decode_uint32(&buf[16]);
                    uint token_count = Marshalls.decode_uint32(&buf[20]);

                    byte* b = buf;

                    b = &buf[24];
                    total_len -= 24;

                    _identifiers = new List<StringName>();
                    for (int i = 0; i < identifier_count; i++)
                    {
                        uint len = Marshalls.decode_uint32(b);
                        if (len > total_len)
                            return Error.ERR_INVALID_DATA;
                        
                        b += 4;
                        
                        byte[] cs = new byte[len];
                        
                        for (int j = 0; j < len; j++)
                        {
                            cs[j] = (byte)(b[j] ^ ((byte)0xb6));
                        }

                        cs[cs.Length - 1] = 0;
                        
                        string s = Encoding.UTF8.GetString(cs);
                        
                        b += len;
                        
                        total_len -= len + 4;
                        
                        _identifiers.Add(s);
                    }

                    _constants = new List<Variant>();
                    
                    for (int i = 0; i < constant_count; i++)
                    {
                        Variant v = new Variant();
                        
                        uint len = 0;
                        
                        Error err = Marshalls.decode_variant(ref v, b, total_len, ref len);
                        
                        if (err != Error.OK)
                            return err;
                        
                        b += len;
                        
                        total_len -= len;
                        
                        _constants.Add(v);

                    }

                    if (line_count * 8 > total_len)
                        return Error.ERR_INVALID_DATA;

                    _lines = new Dictionary<uint, uint>();
                    for (int i = 0; i < line_count; i++)
                    {

                        uint token = Marshalls.decode_uint32(b);
                        b += 4;
                        
                        uint linecol = Marshalls.decode_uint32(b);
                        b += 4;

                        _lines.Add(token, linecol);
                        
                        total_len -= 8;
                    }

                    _tokens = new List<GDTokenizer.Token>();
                    for (int i = 0; i < token_count; i++)
                    {
                        if (total_len < 1)
                            return Error.ERR_INVALID_DATA;

                        if (( (int)(*b) & TOKEN_BYTE_MASK ) != 0)
                        {
                            //little endian always
                            if (total_len < 4)
                                return Error.ERR_INVALID_DATA;

                            _tokens.Add((GDTokenizer.Token)(Marshalls.decode_uint32(b) & ~TOKEN_BYTE_MASK));
                            b += 4;
                        }
                        else
                        {
                            _tokens.Add((GDTokenizer.Token)Enum.ToObject(typeof(GDTokenizer.Token), *b));
                            b += 1;
                            total_len--;
                        }
                    }

                    _token = 0;

                    return Error.OK;
                }
            }
        }

        public List<byte> parse_code_string(string p_code)
        {
            List<byte> buf = new List<byte>();

            Dictionary<StringName, int> identifier_map = new Dictionary<StringName, int>();
            Dictionary<Variant, int> constant_map = new Dictionary<Variant, int>();
            Dictionary<uint, int> line_map = new Dictionary<uint, int>();
            List<int> token_array = new List<int>();

            GDTokenizerText tt = new GDTokenizerText();
            tt.set_code(p_code);
            int line = -1;
            int col = 0;

            while (true)
            {
                if (tt.get_token_line() != line)
                {

                    line = tt.get_token_line();
                    line_map[(uint)line] = token_array.Count;
                }

                int token = (int)tt.get_token();
                switch (tt.get_token())
                {

                    case GDTokenizer.Token.TK_IDENTIFIER: {
                        StringName id = tt.get_token_identifier();
                        if (!identifier_map.ContainsKey(id))
                        {
                            int idx = identifier_map.Count;
                            identifier_map[id] = idx;
                        }
                        token |= identifier_map[id] << TOKEN_BITS;
                    } break;

                    case GDTokenizer.Token.TK_CONSTANT: {
                        Variant c = tt.get_token_constant();
                        if (!constant_map.ContainsKey(c))
                        {
                            int idx = constant_map.Count;
                            constant_map[c] = idx;
                        }
                        token |= constant_map[c] << TOKEN_BITS;
                    } break;

                    case GDTokenizer.Token.TK_BUILT_IN_TYPE: {
                        token |= ((int)tt.get_token_type()) << TOKEN_BITS;
                    } break;

                    case GDTokenizer.Token.TK_BUILT_IN_FUNC: {
                        token |= ((int)tt.get_token_built_in_func()) << TOKEN_BITS;
                    } break;

                    case GDTokenizer.Token.TK_NEWLINE: {
                        token |= tt.get_token_line_indent() << TOKEN_BITS;
                    } break;

                    case GDTokenizer.Token.TK_ERROR: {
                        return new List<byte>();
                    } break;

                    default: break;
                };

                token_array.Add(token);

                if (tt.get_token() == GDTokenizer.Token.TK_EOF)
                    break;

                tt.advance();
            }

            //reverse maps
            Dictionary<int, StringName> rev_identifier_map = new Dictionary<int, StringName>();
            foreach(KeyValuePair<StringName, int> E in identifier_map)
            {
                rev_identifier_map[E.Value] = E.Key;
            }

            Dictionary<int, Variant> rev_constant_map = new Dictionary<int, Variant>();
            foreach (KeyValuePair<Variant, int> E in constant_map)
            {
                rev_constant_map[E.Value] = E.Key;
            }

            Dictionary<int, uint> rev_line_map = new Dictionary<int, uint>();
            foreach (KeyValuePair<uint, int> E in line_map)
            {
                rev_line_map[E.Value] = E.Key;
            }

            //save header
            byte[] tmp = new byte[4];

            unsafe
            {
                fixed (byte* tmp_ptr = tmp)
                {
                    buf.Add((byte)'G');
                    buf.Add((byte)'D');
                    buf.Add((byte)'S');
                    buf.Add((byte)'C');

                    Marshalls.encode_uint32(BYTECODE_VERSION, tmp_ptr);
                    buf.AddRange(tmp);

                    Marshalls.encode_uint32((uint)identifier_map.Count, tmp_ptr);
                    buf.AddRange(tmp);

                    Marshalls.encode_uint32((uint)constant_map.Count, tmp_ptr);
                    buf.AddRange(tmp);

                    Marshalls.encode_uint32((uint)line_map.Count, tmp_ptr);
                    buf.AddRange(tmp);

                    Marshalls.encode_uint32((uint)token_array.Count, tmp_ptr);
                    buf.AddRange(tmp);

                    //save identifiers
                    foreach(KeyValuePair<int, StringName> E in rev_identifier_map)
                    {
                        byte[] cs = Encoding.UTF8.GetBytes((string)E.Value);
                        int len = cs.Length + 1;
                        int extra = 4 - (len % 4);
                        if (extra == 4)
                            extra = 0;

                        Marshalls.encode_uint32((uint)(len + extra), tmp_ptr);

                        for (int i = 0; i < 4; i++)
                        {
                            buf.Add(tmp_ptr[i]);
                        }

                        for (int i = 0; i < len; i++)
                        {
                            buf.Add((byte)(cs[i] ^ 0xb6));
                        }

                        for (int i = 0; i < extra; i++)
                        {
                            buf.Add((byte)(0 ^ 0xb6));
                        }
                    }

                    foreach(KeyValuePair<int, Variant> E in rev_constant_map)
                    {                     
                        int len = 0;
                        Error err = Marshalls.encode_variant(E.Value, null, ref len);

                        if (err != Error.OK)
                            return new List<byte>();

                        int pos = buf.Count;
                        byte[] var_buf = new byte[len];

                        fixed (byte* tmp_var_ptr = var_buf)
                        {
                            Marshalls.encode_variant(E.Value, tmp_var_ptr, ref len);
                            buf.AddRange(var_buf);
                        }
                    }

                    foreach(KeyValuePair<int, uint> E in rev_line_map)
                    {
                        byte[] ibuf = new byte[8];
                        fixed (byte* ibuf_ptr = ibuf)
                        {
                            Marshalls.encode_uint32((uint)E.Key, &ibuf_ptr[0]);
                            Marshalls.encode_uint32(E.Value, &ibuf_ptr[4]);
                            for (int i = 0; i < 8; i++)
                                buf.Add(ibuf[i]);
                        }
                    }

                    for (int count = 0; count < token_array.Count; count++)
                    {
                        uint token = (uint)token_array[count];

                        if ((token & ~TOKEN_MASK) != 0)
                        {
                            Marshalls.encode_uint32((uint)(token_array[count] | TOKEN_BYTE_MASK), tmp_ptr);
                            for (int j = 0; j < 4; j++)
                                buf.Add(tmp[j]);
                        }
                        else
                        {
                            buf.Add((byte)token);
                        }
                    }

                    return buf;
                }
            }
        }
    }
}
